
// Socket pool management
var socketPool = {

    _pool     : {},
    _channels : {},
    _userIds  : {},

    onUserEnter : null,
    onUserLeave : null,

    // DEBUG
    debugPrintSocket: function() {

        var msg = '\nSocket Pool:\n';

        for (var i in this._pool) {
            msg += '  SID: ' + i.substr(0, 6) + ' UID:' +
                    this._pool[i].userId.substr(-6, 6) +
                    ' CID:' + this._pool[i].channelId + '\n';
        }

        console.log(msg);

        return this;
    },

    // DEBUG
    debugPrintChannels: function() {

        var msg = '\nChannel Pool:\n';

        for (var i in this._channels) {

            msg += '  Channel: ' + i + '\n';

            for (var j in this._channels[i]) {
                msg += '    ' + j.substr(0, 6) + '\n';
            }
        }

        console.log(msg);

        return this;
    },

    // DEBUG
    debugPrintUserIds: function() {

        var msg = '\nUserId Pool:\n';

        for (var i in this._userIds) {
            msg += '  UID: ' + i.substr(-6, 6) + ' SID:' +
                    this._userIds[i].substr(0, 6) + '\n';
        }

        console.log(msg);

        return this;
    },

    registerSocket: function(socket) {

        this.unregisterSocket(socket.id);

        this._pool[socket.id] = {
            socket    : socket,
            userId    : '',
            channelId : ''
        };

        return this;
    },

    unregisterSocket: function(socketId) {

        var socketObj = this._pool[socketId];

        if (socketObj) {

            if (socketObj.userId) {
                delete this._userIds[socketObj.userId];
                this.onUserLeave && this.onUserLeave(socketObj.userId);
            }

            if (socketObj.channelId) {
                delete this._channels[socketObj.channelId][socketId];
            }

            delete this._pool[socketId];
        }

        return this;
    },

    registerUserId: function(socketId, userId) {

        var socketObj = this._pool[socketId];

        if (socketObj) {
            socketObj.userId = userId;
            this._userIds[userId] = socketId;
        }

        this.onUserEnter && this.onUserEnter(userId);

        return this;
    },

    registerChannel: function(socketId, channelId) {

        var socketObj = this._pool[socketId];

        if (socketObj) {

            if (socketObj.channelId) {
                delete this._channels[socketObj.channelId][socketId];
            }

            socketObj.channelId = channelId;
            this._channels[channelId] || (this._channels[channelId] = {});
            this._channels[channelId][socketId] = socketObj;
        }

        return this;
    },

    opAll: function(operation) {

        for (var i in this._pool) {
            operation(this._pool[i].socket);
        }

        return this;
    },

    opChannel: function(channelId, operation) {

        var channel = this._channels[channelId];

        if (channel) {
            for (var i in channel) {
                operation(channel[i].socket);
            }
        }

        return this;
    },

    opUser: function(userId, operation) {
        this._userIds[userId] &&
                operation(this._pool[this._userIds[userId]].socket);
        return this;
    }
};

// Pushserver init
var httpServer = require('http').createServer();

var socketServer = require('sockjs').createServer({
    sockjs_url: 'http://cdn.jsdelivr.net/sockjs/0.3.4/sockjs.min.js'
});

var signSocketPool = {},
    signInfoPool   = {};

function autoCleanSign(sign) {

    var lifeTime = 10000;

    setTimeout(function() {
        delete signSocketPool[sign];
        delete signInfoPool[sign];
    }, lifeTime);
}

// Log method
function log(msg) {
    // console.log(msg);
}

// Bind sever events
socketServer.on('connection', function(sock) {

    log('Socket ' + sock.id + ' connected');

    // Register socket to socketPool
    socketPool.registerSocket(sock);

    // DEBUG
    // socketPool.debugPrintSocket();
    // socketPool.debugPrintUserIds();
    // socketPool.debugPrintChannels();

    // Unregister socket on disconnection
    sock.on('close', function() {

        log('Socket ' + sock.id + ' disconnected');

        socketPool.unregisterSocket(sock.id);

        // DEBUG
        // socketPool.debugPrintSocket();
        // socketPool.debugPrintUserIds();
        // socketPool.debugPrintChannels();
    });

    // Handle cmd from user
    sock.on('data', function(cmd) {

        log('Socket ' + sock.id + ' send command ' + cmd);

        var sign = cmd;
            info = signInfoPool[sign];

        if (info) {
            info.userId    !== null && socketPool.registerUserId(sock.id, info.userId);
            info.channelId !== null && socketPool.registerChannel(sock.id, info.channelId);
            delete signInfoPool[sign];

        } else {
            signSocketPool[sign] = sock.id;
            autoCleanSign(sign);
        }

        // DEBUG
        // socketPool.debugPrintSocket();
        // socketPool.debugPrintUserIds();
        // socketPool.debugPrintChannels();
        // console.log(signSocketPool);
        // console.log(signInfoPool);
    });
});

// ****************************************************************************
// Interfaces
// ****************************************************************************

// socketOpts: { port: 8888, prefix: '/push' }
exports.init = function(socketOpts, onUserEnter, onUserLeave) {

    socketServer.installHandlers(httpServer, {
        prefix : socketOpts.prefix || '/push'
    });

    httpServer.listen(socketOpts.port || 8888, '0.0.0.0');

    socketPool.onUserEnter = onUserEnter;
    socketPool.onUserLeave = onUserLeave;

    return socketServer;
};

// NOTIFY: channelId must NOT be 0 !!!!!!
exports.register = function(channelId, userId, sign) {

    if (signSocketPool[sign]) {
        (userId    !== null) && socketPool.registerUserId(signSocketPool[sign], userId);
        (channelId !== null) && socketPool.registerChannel(signSocketPool[sign], channelId);
        delete signSocketPool[sign];

    } else {
        signInfoPool[sign] = {
            userId    : userId,
            channelId : channelId
        };
        autoCleanSign(sign);
    }

    // DEBUG
    // socketPool.debugPrintSocket();
    // socketPool.debugPrintUserIds();
    // socketPool.debugPrintChannels();
    // console.log(signSocketPool);
    // console.log(signInfoPool);
};

exports.pushUserId = function(dataStr, userId) {
    socketPool.opUser(userId, function(socket) {
        socket.write(dataStr);
    });
};

exports.pushChannel = function(dataStr, channelId) {
    socketPool.opChannel(channelId, function(socket) {
        socket.write(dataStr);
    });
};

exports.pushAll = function(dataStr) {
    socketPool.opAll(function(socket) {
        socket.write(dataStr);
    });
};
